package com.antoco.operation.bigscreen.service;

import com.antoco.operation.bigscreen.config.ChannelConfig;
import com.antoco.operation.bigscreen.model.entity.Sensor;
import com.antoco.operation.bigscreen.service.autogeneration.impl.SensorReportServiceImpl;
import com.antoco.operation.bigscreen.service.autogeneration.intf.SensorService;
import com.antoco.operation.bigscreen.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Websocket服务类
 * @author johnsonpei
 */
@Slf4j
@Component
@ServerEndpoint(value = "/websocket")
public class WebSocketServer implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    /** 频道数据服务类 */
    private ChannelDataService channelDataService;

    /** 频道配置 */
    private ChannelConfig channelConfig = null;

    /** 客户端句柄保存用map */
    private static ConcurrentHashMap<String, ConcurrentHashMap<String, WebSocketServer>> clientSocketSet = new ConcurrentHashMap<>();

    /** 与某个客户端的连接会话，需要通过它来给客户端发送数据 */
    private Session session;

    /** 客户端标识（暂时没有用到） */
    private String clientId = "";
    //传感器表服务类
    private  SensorService sensorService;
    //传感器通信记录表服务类
    private SensorReportServiceImpl sensorReportServiceImpl;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        WebSocketServer.applicationContext = applicationContext;
    }

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        this.clientId = UUID.randomUUID().toString();

        // 加载配置
        if (this.channelConfig == null) {
            this.channelConfig = applicationContext.getBean(ChannelConfig.class);
        }

        // 加载频道数据服务类
        if (this.channelDataService == null) {
            this.channelDataService = applicationContext.getBean(ChannelDataService.class);
        }
        // 加载传感器表服务类
        if (this.sensorService == null) {
            this.sensorService = applicationContext.getBean(SensorService.class);
        }

        // 加载传感器通信记录表服务类
        if (this.sensorReportServiceImpl == null) {
            this.sensorReportServiceImpl = applicationContext.getBean(SensorReportServiceImpl.class);
        }

        try {
            // 发送连接成功的欢迎消息
            sendMessage("欢迎加入大屏项目!");
        } catch (IOException e) {
            System.out.println(e.getMessage());
            log.error("websocket IO异常");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        // 从set中删除
        removeWSClientAll(this.clientId);
    }

    /**
     * 客户端发来消息是的处理
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        // 这里只接受订阅，退订的消息
        try {
            JSONObject clientJsonMessage = new JSONObject(message);

            String channelName = "";
            String subscribe = "";
            String idStr = "";
            if (clientJsonMessage.has("subscribe")) {
                subscribe = clientJsonMessage.getString("subscribe");
                int idx = subscribe.lastIndexOf("@");
                if (idx != -1){
                    channelName = subscribe.substring(0, idx);
                    idStr = subscribe.substring(idx + 1);
                }else{
                    channelName = subscribe;
                }

                //当订阅的频道设备的时候，还需要校验id参数
                if("SENSO".equals(channelName)) {
                    if(StringUtils.isNotEmpty(idStr)){
                        if(StringUtil.isNumeric(idStr)){//判断设备id是否为数字
                            Long id = Long.valueOf(idStr);
                            Sensor sensor = sensorService.findById(id).orElse(null);
                            if(sensor != null){
                                // 频道名有效时，将ws实例加入相应的列表中
                                this.putWSClient(channelName, this.clientId, this);
                                net.sf.json.JSONObject pushMessageJsonObj = channelDataService.getSENSOResult(channelName,idStr);
                                send2Channel(channelName, pushMessageJsonObj.toString());
                            }else{
                                sendMessage("该设备id无数据");
                            }
                        }else{
                            sendMessage("订阅设备频道id参数不合法，只能为数字");
                        }
                    }else{
                        sendMessage("订阅设备频道需要id参数");
                    }
                }else if("SENSOR_REPORT".equals(channelName)) {
                    if(StringUtils.isNotEmpty(idStr)){
                        if(StringUtil.isNumeric(idStr)){
                            Long id = Long.valueOf(idStr);
                            // 频道名有效时，将ws实例加入相应的列表中
                            this.putWSClient(channelName, this.clientId, this);
                            JSONObject result = null;
                            try {
                                result = sensorReportServiceImpl.findAllBySensorId(id);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            send2Channel(channelName, result.toString());
                        }else{
                            sendMessage("订阅SENSOR_REPORT频道id参数不合法，只能为数字");
                        }
                    }else{
                        sendMessage("订阅SENSOR_REPORT需要id参数");
                    }
                }else if("WATER_AMOUNT".equals(channelName)) {
                    this.putWSClient(channelName, this.clientId, this);
                    net.sf.json.JSONObject pushMessageJsonObj = channelDataService.getSuiData(channelName,"2");
                    send2Channel(channelName, pushMessageJsonObj.toString());
                }else if("WATER_QUALITY".equals(channelName)) {
                    this.putWSClient(channelName, this.clientId, this);
                    net.sf.json.JSONObject pushMessageJsonObj = channelDataService.getSuiData(channelName,"1");
                    send2Channel(channelName, pushMessageJsonObj.toString());
                }
                else{
                    // 频道名有效时，将ws实例加入相应的列表中
                    this.putWSClient(channelName, this.clientId, this);

                    // 推送符合条件的数据
                    JSONObject pushMessageJsonObj = channelDataService.getChannelAllData(channelName);
                    send2Channel(channelName, pushMessageJsonObj.toString());
                }
            } else if (clientJsonMessage.has("unsubscribe")) {
                channelName = clientJsonMessage.getString("unsubscribe");
                // 从频道中移除
                this.removeWSClient(channelName, this.clientId);
            } else if (clientJsonMessage.has("channel") && clientJsonMessage.has("message")) {
                channelName = clientJsonMessage.getString("channel");
                String clientMessage = clientJsonMessage.getString("message");

                send2Channel(channelName, clientMessage);
            }
        } catch (IOException e) {
            log.error("处理失败 => {}", message);
            e.printStackTrace();
        }
    }

    /**
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误 => {}", error.getMessage());
        error.printStackTrace();
    }

    /**
     * 发送消息给当前连接
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        if (this.session.isOpen()) {
            this.session.getBasicRemote().sendText(message);
        } else {
            clientSocketSet.remove(this.clientId);
        }
    }

    /**
     * 广播消息
     * @param message
     * @throws IOException
     */
    public void send2Channel(String channelName, String message) throws IOException {
        if (clientSocketSet.containsKey(channelName)) {
            for (String key : clientSocketSet.get(channelName).keySet()) {
                try {
                    clientSocketSet.get(channelName).get(key).sendMessage(message);
                } catch (IOException e) {
                    log.error("发送信息失败 => {}", message);
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 添加客户端句柄
     *
     * @param channelName
     * @param clientId
     * @param wsClient
     */
    private void putWSClient(String channelName, String clientId, WebSocketServer wsClient) {
        try {
            // 获取配置
            Map<String, String> channels = channelConfig.getChannels();
            if (channels.containsKey(channelName)) {
                if (!clientSocketSet.containsKey(channelName)) {
                    clientSocketSet.put(channelName, new ConcurrentHashMap<String, WebSocketServer>());
                }

                clientSocketSet.get(channelName).put(clientId, wsClient);
                sendMessage("欢迎订阅「" + channelName + "」频道!");
            } else {
                sendMessage("未知频道!");
            }
        } catch (IOException e) {
            log.error("发送信息失败 => {}", e.getMessage());
        }
    }

    /**
     * 移出指定频道的客户端句柄
     *
     * @param channelName
     * @param clientId
     */
    private void removeWSClient(String channelName, String clientId) {
        if (clientSocketSet.containsKey(channelName)) {
            clientSocketSet.get(channelName).remove(clientId);

            try {
                sendMessage("已成功退订「" + channelName + "」频道!");
            } catch (IOException e) {
                log.error("发送信息失败 => {}", e.getMessage());
            }
        }
    }

    /**
     * 移出所有频道的客户端句柄
     *
     * @param clientId
     */
    private void removeWSClientAll(String clientId) {
        for (String channelName : clientSocketSet.keySet()) {
            removeWSClient(channelName, clientId);
        }
    }
}

